package service

import (
	"encoding/json"
	"io/ioutil"
	"meeting-manager/global"
	"meeting-manager/internal/model"
	"meeting-manager/pkg/response"
	"net/http"
	"strings"
	"sync"
	"time"
)

var serverList *model.ServerList

func init() {
	// 初始化服务器列表
	serverList = model.NewServerList()

	// 启动心跳检测
	go heartbeat(5 * time.Second)
}

type RegisterHandler struct{}

// 用于服务器注册
func (reg *RegisterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 获取对方服务器地址，该地址分为 ip 和端口两部分
	// 端口可能并不是提供服务的修改，所以需要修改
	addrX := strings.Split(r.RemoteAddr, ":")

	// 检查 ip 的正确性
	hostSplit := strings.Split(addrX[0], ".")
	if len(hostSplit) != 4 {
		_, _ = w.Write(response.IpErr.ToByte())
		return
	}

	// 从 r.Body 中读取数据
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// 解析读取到的数据
	var data map[string]interface{}
	err = json.Unmarshal(body, &data)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	var port, name string

	// 获取端口号
	port = data["port"].(string)
	if port == "" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// 拼装地址
	addr := addrX[0] + ":" + port

	// 获取请求者的名称
	name = data["name"].(string)
	if name == "" {
		name = addr
	}

	// 创建该服务器的信息表
	info := model.NewServer(name, addr, 10000)

	// 将信息表添加到列表中
	err = serverList.AddServer(model.Address(addr), info)
	if err != nil {
		_, _ = w.Write(response.SeverRegErr.ToByte())
		return
	}

	// 返回成功信息
	_, _ = w.Write(response.Success.ToByte())
}

// 心跳检测
func heartbeat(freq time.Duration) {
	checkList := model.NewCheckList(serverList)

	for {
		// 每隔 freq 秒对所有服务器做一次心跳检测
		time.Sleep(freq)

		// 更新到最新状态
		checkList.UpdateToLatestStatus()

		// 控制方法的执行，检测完所有的服务器之后，才会进行下一轮心跳检测
		var wait sync.WaitGroup

		// 对每一台注册的服务器分别进行检测
		for _, server := range checkList.GetServers() {
			// 计数器加一
			wait.Add(1)

			// 对每个服务器都启动一个 goroutine，并发进行检测
			go func(server *model.Server) {
				// 函数执行结束后计数器减一
				defer wait.Done()

				// 该变量为真时表示该服务信息仍在列表中
				success := true

				// 尝试多次，全都失败之后才会判定该服务器已失联
				for attempts := 0; attempts < 3; attempts++ {
					// 向该服务器发送心跳检测请求
					res, err := http.Get("http://" + string(server.GetAddr()) + "/heartbeat")
					if err != nil || res.StatusCode != http.StatusOK {
						// 首次请求失败时，会暂时将该服务器从列表中移出
						if success {
							success = false
							_, err = checkList.GetMaster().RemoveServer(server.GetAddr())
							if err != nil {
								global.Logger.Errorf(err.Error())
							}
						}
					} else {
						// 若之前连接失败过，会将该服务器重新添加到列表中
						if !success {
							err = checkList.GetMaster().AddServer(server.GetAddr(), server)
							if err != nil {
								global.Logger.Errorf(err.Error())
							}
						}
						break
					}

					// 如果在最后一次心跳检测中，连接结果还是失败
					// 那么将该服务器中的所有会议全部移除
					// 由于该服务器已经移除，所以不用做移除服务器负载的事情
					if attempts == 2 && !success {
						for id, meeting := range meetingList.GetMeetings() {
							if meeting.Addr == server.GetAddr() {
								meetingList.RemoveMeetingById(id)
							}
						}
					}
				}
			}(server)
		}

		// 计数器为零，即所有服务器都检测完毕后，方法才继续执行
		wait.Wait()
	}
}
